package com.genexususa.eo.google.analytics;

import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.services.analytics.model.GaData;
import com.google.api.services.analytics.model.McfData;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;


/**
 * Created with IntelliJ IDEA.
 * User: Nico
 * Date: 19/07/13
 * Time: 04:04 PM
 * To change this template use File | Settings | File Templates.
 */
public class AnalyticsManager {

    private static final String FILE_PATH = "filePath";
    private static final String APPLICATION_NAME = "applicationName";
    private static final String PROFILE_ID = "profileId";
    private static final String START_DATE = "startDate";
    private static final String END_DATE = "endDate";
    private static final String METRICS = "metrics";
    private static final String DIMENSIONS = "dimensions";
    private static final String SORTS = "sorts";
    private static final String FILTERS = "filters";
    private static final String MAX_RESULTS = "maxResults";

    /**
     * Connects to Google Analytics
     * Parses the input XML
     * Runs Query on Google Analytics
     * And returns the result as a GXSDT.toxml() string
     *
     * @param xml
     * @return
     */
    public String executeQuery(String xml){

        String result = "";
        String filePath = "";
        String profileId = "";

        try{

            // Parse xml with Query parameters
            GoogleAnalyticsQuery queryParams = parseXML(xml);

            filePath = queryParams.getFilePath();
            String applicationName = queryParams.getApplicationName();
            profileId = queryParams.getProfileId();
            String startDate = queryParams.getStartDate();
            String endDate = queryParams.getEndDate();
            List<String> metrics = queryParams.getMetrics();
            List<String> dimensions = queryParams.getDimensions();
            List<String> filters = queryParams.getFilters();
            List<String> sortsCol = queryParams.getSortsCol();
            int maxResults = queryParams.getMaxResults();

            // connect to google analytics
            AnalyticsConnector ac = AnalyticsConnector.getInstance();


            // execute query
            GaData queryResult = ac.executeQuery(applicationName, profileId, startDate, endDate, metrics, dimensions, filters, sortsCol, maxResults);

            printQueryInfo(queryResult);
            System.out.println();
            printDataTable(queryResult);

            printTotalsForAllResults(queryResult);

            result = resultToXml(queryResult);
            writeXMLFile(filePath, profileId, result);

            System.out.println();
            System.out.println("Result XML:");
            System.out.println(result);


        }catch (GoogleJsonResponseException e) {

            GoogleAnalyticsResult gaResult = new GoogleAnalyticsResult();
            gaResult.HasError = true;

            GoogleJsonError gaError = e.getDetails();

            ErrorsStruct errors  = new ErrorsStruct();
            errors.Message = gaError.getMessage();
            errors.Code = Integer.toString(gaError.getCode());

            List<GoogleJsonError.ErrorInfo> errorInfoCol =  gaError.getErrors();

            errors.Errors = new ArrayList<Error>();

            for(GoogleJsonError.ErrorInfo errorInfo : errorInfoCol){

                Error error = new Error();
                error.Domain = errorInfo.getDomain();
                error.Reason = errorInfo.getReason();
                error.Message = errorInfo.getMessage();
                error.LocationType = errorInfo.getLocationType();
                error.Location = errorInfo.getLocation();

                errors.Errors.add(error);

            }

            gaResult.Errors = errors;

            result = objectToXML(gaResult);
            writeXMLFile(filePath, profileId, result);
            System.err.println(result);

            return result;

        }catch (Exception e){

            GoogleAnalyticsResult gaResult =  new GoogleAnalyticsResult();
            gaResult.HasError = true;

            ErrorsStruct errors  = new ErrorsStruct();
            errors.UnknownError = e.getMessage();

            gaResult.Errors = errors;
            //e.printStackTrace();

            result = objectToXML(gaResult);
            writeXMLFile(filePath, profileId, result);
            System.err.println(result);

            return result;
        }


        return result;
    }

    public String getTop10Keywords(String xml){

        String result = "";
        String profileId = "";
        String filePath = "";

        try{

            // Parse xml with Query parameters
            GoogleAnalyticsQuery queryParams = parseXML(xml);

            filePath = queryParams.getFilePath();
            String applicationName = queryParams.getApplicationName();
            profileId = queryParams.getProfileId();
            String startDate = queryParams.getStartDate();
            String endDate = queryParams.getEndDate();
            List<String> metrics = queryParams.getMetrics();
            List<String> dimensions = queryParams.getDimensions();
            List<String> filters = queryParams.getFilters();
            List<String> sortsCol = queryParams.getSortsCol();
            int maxResults = queryParams.getMaxResults();

            // connect to google analytics
            AnalyticsConnector ac = AnalyticsConnector.getInstance();

            // execute query
            GaData queryResult = ac.executeQuery(applicationName, profileId, startDate, endDate, metrics, dimensions, filters, sortsCol, maxResults);

            result = top10resultToXML(queryResult);

            writeXMLFile(filePath, profileId, result);

            // prints to console
            printQueryInfo(queryResult);
            System.out.println();
            printDataTable(queryResult);
            System.out.println();
            System.out.println("Result XML:");
            System.out.println(result);

            return result;

        }catch (GoogleJsonResponseException e) {

            Top10KeywordsResult gaResult = new Top10KeywordsResult();
            gaResult.HasError = true;

            GoogleJsonError gaError = e.getDetails();

            ErrorsStruct errors  = new ErrorsStruct();
            errors.Message = gaError.getMessage();
            errors.Code = Integer.toString(gaError.getCode());

            List<GoogleJsonError.ErrorInfo> errorInfoCol =  gaError.getErrors();

            errors.Errors = new ArrayList<Error>();

            for(GoogleJsonError.ErrorInfo errorInfo : errorInfoCol){

                Error error = new Error();
                error.Domain = errorInfo.getDomain();
                error.Reason = errorInfo.getReason();
                error.Message = errorInfo.getMessage();
                error.LocationType = errorInfo.getLocationType();
                error.Location = errorInfo.getLocation();

                errors.Errors.add(error);

            }

            gaResult.Errors = errors;

            result = objectToXML(gaResult);
            System.err.println(result);

            return result;

        }catch (Exception e){

            Top10KeywordsResult gaResult =  new Top10KeywordsResult();
            gaResult.HasError = true;

            ErrorsStruct errors  = new ErrorsStruct();
            errors.UnknownError = e.getMessage();

            gaResult.Errors = errors;

            result = objectToXML(gaResult);
            System.err.println(result);

            return result;
        }



    }

    public String getConversions(String xml) {

        String result = "";
        String filePath = "";
        String profileId = "";

        try{

            // Parse xml with Query parameters
            GoogleAnalyticsQuery queryParams = parseXML(xml);

            filePath = queryParams.getFilePath();
            String applicationName = queryParams.getApplicationName();
            profileId = queryParams.getProfileId();
            String startDate = queryParams.getStartDate();
            String endDate = queryParams.getEndDate();
            List<String> metrics = queryParams.getMetrics();
            List<String> dimensions = queryParams.getDimensions();
            List<String> filters = queryParams.getFilters();
            List<String> sortsCol = queryParams.getSortsCol();
            int maxResults = queryParams.getMaxResults();

            // connect to google analytics
            AnalyticsConnector ac = AnalyticsConnector.getInstance();


            // execute query
            McfData queryResult = ac.executeConversionsQuery(applicationName, profileId, startDate, endDate, metrics, dimensions, filters, sortsCol, maxResults);

            printQueryInfo(queryResult);
            System.out.println();
            printDataTable(queryResult);

            printTotalsForAllResults(queryResult);

            result = resultToXml(queryResult);
            writeXMLFile(filePath, profileId, result);

            System.out.println();
            System.out.println("Result XML:");
            System.out.println(result);


        }catch (GoogleJsonResponseException e) {

            GoogleAnalyticsResult gaResult = new GoogleAnalyticsResult();
            gaResult.HasError = true;

            GoogleJsonError gaError = e.getDetails();

            ErrorsStruct errors  = new ErrorsStruct();
            errors.Message = gaError.getMessage();
            errors.Code = Integer.toString(gaError.getCode());

            List<GoogleJsonError.ErrorInfo> errorInfoCol =  gaError.getErrors();

            errors.Errors = new ArrayList<Error>();

            for(GoogleJsonError.ErrorInfo errorInfo : errorInfoCol){

                Error error = new Error();
                error.Domain = errorInfo.getDomain();
                error.Reason = errorInfo.getReason();
                error.Message = errorInfo.getMessage();
                error.LocationType = errorInfo.getLocationType();
                error.Location = errorInfo.getLocation();

                errors.Errors.add(error);

            }

            gaResult.Errors = errors;

            result = objectToXML(gaResult);
            writeXMLFile(filePath, profileId, result);
            System.err.println(result);

            return result;

        }catch (Exception e){

            GoogleAnalyticsResult gaResult =  new GoogleAnalyticsResult();
            gaResult.HasError = true;

            ErrorsStruct errors  = new ErrorsStruct();
            errors.UnknownError = e.getMessage();

            gaResult.Errors = errors;
            //e.printStackTrace();

            result = objectToXML(gaResult);
            writeXMLFile(filePath, profileId, result);
            System.err.println(result);

            return result;
        }


        return result;
    }

    private GoogleAnalyticsQuery parseXML(String xml) throws Exception{


        GoogleAnalyticsQuery queryParams = new GoogleAnalyticsQuery();

        Util util = new Util();

        Document doc = util.loadXMLFromString(xml);

        XPathFactory factory = XPathFactory.newInstance();
        XPath xpath = factory.newXPath();

        XPathExpression expr
                = xpath.compile("//GoogleAnalyticsQuery");

        Object queriesNodes = expr.evaluate(doc, XPathConstants.NODESET);

        NodeList queries = (NodeList) queriesNodes;

        if (queries.getLength() > 0) {

            for (int i = 0; i < queries.getLength(); i++) { // For all Query Nodes

                Node n = queries.item(i);

                NodeList children = n.getChildNodes();

                if (children.getLength() > 0) {

                    for (int j = 0; j < children.getLength(); j++) {

                        // filePath
                        if (children.item(j).getNodeName().equalsIgnoreCase(Constants.FILE_PATH.value())) {
                            queryParams.setFilePath(children.item(j).getTextContent());
                        }

                        // applicationName
                        if (children.item(j).getNodeName().equalsIgnoreCase(Constants.APPLICATION_NAME.value())) {
                            queryParams.setApplicationName(children.item(j).getTextContent());
                        }

                        // profileId
                        if (children.item(j).getNodeName().equalsIgnoreCase(Constants.PROFILE_ID.value())) {
                            queryParams.setProfileId(children.item(j).getTextContent());
                        }

                        // startDate
                        if (children.item(j).getNodeName().equalsIgnoreCase(Constants.START_DATE.value())) {
                            queryParams.setStartDate(children.item(j).getTextContent());
                        }

                        // endDate
                        if (children.item(j).getNodeName().equalsIgnoreCase(Constants.END_DATE.value())) {
                            queryParams.setEndDate(children.item(j).getTextContent());
                        }

                        // metrics
                        if (children.item(j).getNodeName().equalsIgnoreCase(Constants.METRICS.value())) {
                            NodeList metricsNodes = children.item(j).getChildNodes();
                            queryParams.setMetrics(getParamsListFromNodes(metricsNodes));
                        }

                        // dimensions
                        if (children.item(j).getNodeName().equalsIgnoreCase(Constants.DIMENSIONS.value())) {
                            NodeList metricsNodes = children.item(j).getChildNodes();
                            queryParams.setDimensions(getParamsListFromNodes(metricsNodes));
                        }

                        // filters
                        if (children.item(j).getNodeName().equalsIgnoreCase(Constants.FILTERS.value())) {
                            NodeList filtersNodes = children.item(j).getChildNodes();
                            queryParams.setFilters(getParamsListFromNodes(filtersNodes));
                        }

                        // sorts
                        if (children.item(j).getNodeName().equalsIgnoreCase(Constants.SORTS.value())) {
                            NodeList metricsNodes = children.item(j).getChildNodes();
                            queryParams.setSortsCol(getParamsListFromNodes(metricsNodes));
                        }

                        // maxResults
                        if (children.item(j).getNodeName().equalsIgnoreCase(Constants.MAX_RESULTS.value())) {
                            queryParams.setMaxResults(Integer.parseInt(children.item(j).getTextContent()));
                        }

                    }

                }

            }
        }

        return queryParams ;
    }

    /**
     * Creates a comma separated string from a list of nodes
     *
     * @param nodeList
     * @return
     * @throws Exception
     */
    private List<String> getParamsListFromNodes(NodeList nodeList) throws Exception{
        if (nodeList.getLength() == 0) return null;
        List<String> params = new ArrayList<String>();

        for (int j = 0; j < nodeList.getLength(); j++) {

            String param = nodeList.item(j).getTextContent();
            if (param.trim().length() > 0)
                params.add(param);
        }
        return params;
    }

    private String resultToXml(GaData resultData) throws Exception{

        GoogleAnalyticsResult gaResult = new GoogleAnalyticsResult();

        Map<String, String> totals = resultData.getTotalsForAllResults();

        for (Map.Entry<String,String> entry : totals.entrySet()) {

            String metric = entry.getKey();
            String value = entry.getValue();

            if (metric.equals(Constants.VISITS.value())){
                gaResult.TotalVisits =  Float.parseFloat(value);
            }

            if (metric.equals(Constants.UNIQUE_VISITORS.value())){
                gaResult.TotalUniqueVisits =  Float.parseFloat(value);
            }

            if (metric.equals(Constants.PAGE_VIEWS.value())){
                gaResult.TotalPageviews =  Float.parseFloat(value);
            }

            if (metric.equals(Constants.PAGES_PER_VISIT.value())){
                gaResult.PagesPerVisit =  Float.parseFloat(value);
            }

            if (metric.equals(Constants.AVG_VISIT_DURATION.value())){
                gaResult.AvgVisitDuration =  Float.parseFloat(value);
            }

            if (metric.equals(Constants.BOUNCE_RATE.value())){
                gaResult.BounceRate =  Float.parseFloat(value);
            }

            if (metric.equals(Constants.NEW_VISITS_PERCENTAGE.value())){
                gaResult.NewVisitsPercentage =  Float.parseFloat(value);
            }

            if (metric.equals(Constants.NON_BRANDED_VISITS.value())){
                gaResult.TotalNonBrandedVisits =  Float.parseFloat(value);
            }

            if (metric.equals(Constants.ASSISTED_CONVERSIONS.value())){
                gaResult.AssistedConversions =  Float.parseFloat(value);
            }

            System.out.println(entry.getKey() + " : " + entry.getValue());

        }

        return objectToXML(gaResult);

        /*
        if (resultData.getTotalResults() > 0) {

            // Load the column names.
            for (GaData.ColumnHeaders header : resultData.getColumnHeaders()) {
                GoogleAnalyticsResultItem gaResultItem = new GoogleAnalyticsResultItem();
                gaResultItem.value = header.getName();
                gaResult.columns.add(gaResultItem);
            }

            // Load the rows of data.
            for (List<String> rowValues : resultData.getRows()) {
                for (String value : rowValues) {
                    GoogleAnalyticsResultItem gaResultItem = new GoogleAnalyticsResultItem();
                    gaResultItem.value = value;
                    gaResult.rows.add(gaResultItem);
                }

            }
        } else {

            ErrorsStruct errors =  new ErrorsStruct();
            gaResult.HasError = true;
            errors.UnknownError = "Query returned no data from Google Analytics";

            gaResult.Errors = errors;

        }
        */

    }

    private String resultToXml(McfData resultData) throws Exception{

        GoogleAnalyticsResult gaResult = new GoogleAnalyticsResult();

        Map<String, String> totals = resultData.getTotalsForAllResults();

        for (Map.Entry<String,String> entry : totals.entrySet()) {

            String metric = entry.getKey();
            String value = entry.getValue();

            if (metric.equals(Constants.VISITS.value())){
                gaResult.TotalVisits =  Float.parseFloat(value);
            }

            if (metric.equals(Constants.UNIQUE_VISITORS.value())){
                gaResult.TotalUniqueVisits =  Float.parseFloat(value);
            }

            if (metric.equals(Constants.PAGE_VIEWS.value())){
                gaResult.TotalPageviews =  Float.parseFloat(value);
            }

            if (metric.equals(Constants.PAGES_PER_VISIT.value())){
                gaResult.PagesPerVisit =  Float.parseFloat(value);
            }

            if (metric.equals(Constants.AVG_VISIT_DURATION.value())){
                gaResult.AvgVisitDuration =  Float.parseFloat(value);
            }

            if (metric.equals(Constants.BOUNCE_RATE.value())){
                gaResult.BounceRate =  Float.parseFloat(value);
            }

            if (metric.equals(Constants.NEW_VISITS_PERCENTAGE.value())){
                gaResult.NewVisitsPercentage =  Float.parseFloat(value);
            }

            if (metric.equals(Constants.NON_BRANDED_VISITS.value())){
                gaResult.TotalNonBrandedVisits =  Float.parseFloat(value);
            }

            if (metric.equals(Constants.ASSISTED_CONVERSIONS.value())){
                gaResult.AssistedConversions =  Float.parseFloat(value);
            }

            System.out.println(entry.getKey() + " : " + entry.getValue());

        }

        return objectToXML(gaResult);
    }

    private String top10resultToXML(GaData resultData) throws Exception{

        Top10KeywordsResult gaResult = new Top10KeywordsResult();

        if (resultData.getTotalResults() > 0) {

            // Load the rows of data.
            for (List<String> rowValues : resultData.getRows()) {
                Top10KeywordsResultItem gaResultItem = new Top10KeywordsResultItem();
                int count = 1;
                for (String value : rowValues) {

                    if (count == 1)
                        gaResultItem.Keyword = value;

                    if (count == 2)
                        gaResultItem.Percentage = Float.parseFloat(value);

                    if (count == 3){
                        Map<String, String> totals = resultData.getTotalsForAllResults();
                        for (Map.Entry<String,String> entry : totals.entrySet()) {

                            String metric = entry.getKey();
                            String val = entry.getValue();

                            if (metric.equals(Constants.VISITS.value())){
                                gaResultItem.TotalVisits = Float.parseFloat(val);
                            }
                        }
                    }

                    if (count == 4)
                        gaResultItem.KeywordVisits = Float.parseFloat(value);


                    count ++;
                }
                gaResult.Results.add(gaResultItem);
            }
        } else {

            ErrorsStruct errors =  new ErrorsStruct();
            gaResult.HasError = true;
            errors.UnknownError = "Query returned no data from Google Analytics";

            gaResult.Errors = errors;

        }

        return objectToXML(gaResult);


    }

    private static String objectToXML(IAPIResult gaResult){
        String result = "";
        try {

            JAXBContext contextObj = JAXBContext.newInstance(gaResult.getClass());

            Marshaller marshallerObj = contextObj.createMarshaller();
            marshallerObj.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

            StringWriter sw = new StringWriter();

            marshallerObj.marshal(gaResult, sw);
            result = sw.getBuffer().toString();

            return result;

        } catch (Exception ex) {
            ex.printStackTrace();
            result = ex.getMessage();
            return result;
        }
    }

    private static void writeXMLFile(String filePath, String profileId, String xml){
        try {

            Date date = new Date();
            SimpleDateFormat dateFormat = new SimpleDateFormat("MMddYYYY");
            String fileName = filePath + "_" + dateFormat.format(date) + "_" + profileId + ".xml";

            FileWriter fw = new FileWriter(fileName);
            BufferedWriter bw = new BufferedWriter(fw);

            bw.write(xml);
            bw.flush();
            bw.close();
        }catch (Exception ex){
            ex.printStackTrace();
        }

    }

    /**
     * Prints all the rows of data returned by the API.
     *
     * @param gaData the data returned from the API.
     */
    private static void printDataTable(GaData gaData) {
        if (gaData.getTotalResults() > 0) {
            System.out.println("Data Table:");

            // Print the column names.
            for (GaData.ColumnHeaders header : gaData.getColumnHeaders()) {
                System.out.format("%-32s", header.getName());
            }
            System.out.println();

            // Print the rows of data.
            for (List<String> rowValues : gaData.getRows()) {
                for (String value : rowValues) {
                    System.out.format("%-32s", value);
                }
                System.out.println();
            }
        } else {
            System.out.println("No data");
        }
    }

    private static void printDataTable(McfData mcfData) {
        if (mcfData.getTotalResults() > 0) {
            System.out.println("Data Table:");

            // Print the column names.
            for (McfData.ColumnHeaders header : mcfData.getColumnHeaders()) {
                System.out.format("%-32s", header.getName());
            }
            System.out.println();

            // Print the rows of data.
            for (List<McfData.Rows> rowValues : mcfData.getRows()) {
                for (McfData.Rows value : rowValues) {
                    System.out.format("%-32s", value.getPrimitiveValue());
                }
                System.out.println();
            }
        } else {
            System.out.println("No data");
        }
    }


    private static void printQueryInfo(GaData gaData) {
        GaData.Query query = gaData.getQuery();

        System.out.println("Query Info:");
        System.out.println("Ids: " + query.getIds());
        System.out.println("Start Date: " + query.getStartDate());
        System.out.println("End Date: " + query.getEndDate());
        System.out.println("Metrics: " + query.getMetrics()); // List
        System.out.println("Dimensions: " + query.getDimensions()); // List
        System.out.println("Sort: " + query.getSort()); // List
        System.out.println("Segment: " + query.getSegment());
        System.out.println("Filters: " + query.getFilters());
        System.out.println("Start Index: " + query.getStartIndex());
        System.out.println("Max Results: " + query.getMaxResults());
    }

    private static void printQueryInfo(McfData gaData) {
        McfData.Query query = gaData.getQuery();

        System.out.println("Query Info:");
        System.out.println("Ids: " + query.getIds());
        System.out.println("Start Date: " + query.getStartDate());
        System.out.println("End Date: " + query.getEndDate());
        System.out.println("Metrics: " + query.getMetrics()); // List
        System.out.println("Dimensions: " + query.getDimensions()); // List
        System.out.println("Sort: " + query.getSort()); // List
        System.out.println("Segment: " + query.getSegment());
        System.out.println("Filters: " + query.getFilters());
        System.out.println("Start Index: " + query.getStartIndex());
        System.out.println("Max Results: " + query.getMaxResults());
    }


    private void printTotalsForAllResults(GaData gaData) {
        Map<String, String> totalsMap = gaData.getTotalsForAllResults();

        for (Map.Entry<String,String> entry : totalsMap.entrySet()) {
            System.out.println(entry.getKey() + " : " + entry.getValue());
        }
    }

    private void printTotalsForAllResults(McfData mcfData) {
        Map<String, String> totalsMap = mcfData.getTotalsForAllResults();

        for (Map.Entry<String,String> entry : totalsMap.entrySet()) {
            System.out.println(entry.getKey() + " : " + entry.getValue());
        }
    }

}
